#include "definitions.h"


// *****************************************************************************
// *****************************************************************************
// Section: Data Types
// *****************************************************************************
// *****************************************************************************
/* The following data type definitions are used by the functions in this
    interface and should be considered part it.
*/
typedef enum {
    ADC_POSINPUT_PIN0 = ADC_INPUTCTRL_MUXPOS_PIN0,
    ADC_POSINPUT_PIN1 = ADC_INPUTCTRL_MUXPOS_PIN1,
    ADC_POSINPUT_PIN2 = ADC_INPUTCTRL_MUXPOS_PIN2,
    ADC_POSINPUT_PIN3 = ADC_INPUTCTRL_MUXPOS_PIN3,
    ADC_POSINPUT_PIN4 = ADC_INPUTCTRL_MUXPOS_PIN4,
    ADC_POSINPUT_PIN5 = ADC_INPUTCTRL_MUXPOS_PIN5,
    ADC_POSINPUT_PIN6 = ADC_INPUTCTRL_MUXPOS_PIN6,
    ADC_POSINPUT_PIN7 = ADC_INPUTCTRL_MUXPOS_PIN7,
    ADC_POSINPUT_PIN8 = ADC_INPUTCTRL_MUXPOS_PIN8,
    ADC_POSINPUT_PIN9 = ADC_INPUTCTRL_MUXPOS_PIN9,
    ADC_POSINPUT_PIN10 = ADC_INPUTCTRL_MUXPOS_PIN10,
    ADC_POSINPUT_PIN11 = ADC_INPUTCTRL_MUXPOS_PIN11,
    ADC_POSINPUT_PIN12 = ADC_INPUTCTRL_MUXPOS_PIN12,
    ADC_POSINPUT_PIN13 = ADC_INPUTCTRL_MUXPOS_PIN13,
    ADC_POSINPUT_PIN14 = ADC_INPUTCTRL_MUXPOS_PIN14,
    ADC_POSINPUT_PIN15 = ADC_INPUTCTRL_MUXPOS_PIN15,
    ADC_POSINPUT_PIN16 = ADC_INPUTCTRL_MUXPOS_PIN16,
    ADC_POSINPUT_PIN17 = ADC_INPUTCTRL_MUXPOS_PIN17,
    ADC_POSINPUT_PIN18 = ADC_INPUTCTRL_MUXPOS_PIN18,
    ADC_POSINPUT_PIN19 = ADC_INPUTCTRL_MUXPOS_PIN19,
    ADC_POSINPUT_TEMP = ADC_INPUTCTRL_MUXPOS_TEMP,
    ADC_POSINPUT_BANDGAP = ADC_INPUTCTRL_MUXPOS_BANDGAP,
    ADC_POSINPUT_SCALEDCOREVCC = ADC_INPUTCTRL_MUXPOS_SCALEDCOREVCC,
    ADC_POSINPUT_SCALEDIOVCC = ADC_INPUTCTRL_MUXPOS_SCALEDIOVCC,
    ADC_POSINPUT_DAC = ADC_INPUTCTRL_MUXPOS_DAC,
} ADC_POSINPUT;

// *****************************************************************************

typedef enum {
    ADC_NEGINPUT_PIN0 = ADC_INPUTCTRL_MUXNEG_PIN0,
    ADC_NEGINPUT_PIN1 = ADC_INPUTCTRL_MUXNEG_PIN1,
    ADC_NEGINPUT_PIN2 = ADC_INPUTCTRL_MUXNEG_PIN2,
    ADC_NEGINPUT_PIN3 = ADC_INPUTCTRL_MUXNEG_PIN3,
    ADC_NEGINPUT_PIN4 = ADC_INPUTCTRL_MUXNEG_PIN4,
    ADC_NEGINPUT_PIN5 = ADC_INPUTCTRL_MUXNEG_PIN5,
    ADC_NEGINPUT_PIN6 = ADC_INPUTCTRL_MUXNEG_PIN6,
    ADC_NEGINPUT_PIN7 = ADC_INPUTCTRL_MUXNEG_PIN7,
    ADC_NEGINPUT_GND = ADC_INPUTCTRL_MUXNEG_GND,
    ADC_NEGINPUT_IOGND = ADC_INPUTCTRL_MUXNEG_IOGND,
} ADC_NEGINPUT;


// *****************************************************************************
typedef enum {
    ADC_STATUS_RESRDY = ADC_INTFLAG_RESRDY_Msk,
    ADC_STATUS_WINMON = ADC_INTFLAG_WINMON_Msk,
    /* Force compiler to reserve 32-bit for this enum */
    ADC_STATUS_INVALID = 0xFFFFFFFF
} ADC_STATUS;

// *****************************************************************************
typedef enum {
    ADC_WINMODE_DISABLED = ADC_WINCTRL_WINMODE_DISABLE_Val,
    ADC_WINMODE_GREATER_THAN_WINLT = ADC_WINCTRL_WINMODE_MODE1_Val,
    ADC_WINMODE_LESS_THAN_WINUT = ADC_WINCTRL_WINMODE_MODE2_Val,
    ADC_WINMODE_BETWEEN_WINLT_AND_WINUT = ADC_WINCTRL_WINMODE_MODE3_Val,
    ADC_WINMODE_OUTSIDE_WINLT_AND_WINUT = ADC_WINCTRL_WINMODE_MODE4_Val
} ADC_WINMODE;

#define ADC_LINEARITY0_POS  (27)
#define ADC_LINEARITY0_Msk   ((0x1F << ADC_LINEARITY0_POS))

#define ADC_LINEARITY1_POS  (0)
#define ADC_LINEARITY1_Msk   ((0x7 << ADC_LINEARITY1_POS))

#define ADC_BIASCAL_POS  (3)
#define ADC_BIASCAL_Msk   ((0x7 << ADC_BIASCAL_POS))

// *****************************************************************************
// *****************************************************************************
// Section: ADC Implementation
// *****************************************************************************
// *****************************************************************************

static void ADC_ClockInit (void)
{
    /* Selection of the Generator and write Lock for ADC */
    GCLK_REGS->GCLK_CLKCTRL = GCLK_CLKCTRL_ID (GCLK_CLKCTRL_ID_ADC_Val) |
                              GCLK_CLKCTRL_GEN (GCLK_CLKCTRL_GEN_GCLK0_Val)  |
                              GCLK_CLKCTRL_CLKEN_Msk;

    /* Configure the APBC Bridge Clocks */
    PM_REGS->PM_APBCMASK |= PM_APBCMASK_ADC(1);
}

static void ADC_PortInit (void)
{
    PORT_REGS->GROUP[0].PORT_PINCFG[PIN_PA02B_ADC_AIN0] = PORT_PINCFG_PMUXEN (1);
    PORT_REGS->GROUP[0].PORT_PINCFG[PIN_PA03B_ADC_AIN1] = PORT_PINCFG_PMUXEN (1);
    PORT_REGS->GROUP[0].PORT_PINCFG[PIN_PA04B_ADC_VREFP] = PORT_PINCFG_PMUXEN (1);
    PORT_REGS->GROUP[0].PORT_PINCFG[PIN_PA05B_ADC_AIN5] = PORT_PINCFG_PMUXEN (1);
    PORT_REGS->GROUP[0].PORT_PINCFG[PIN_PA06B_ADC_AIN6] = PORT_PINCFG_PMUXEN (1);
    PORT_REGS->GROUP[0].PORT_PINCFG[PIN_PA07B_ADC_AIN7] = PORT_PINCFG_PMUXEN (1);

    PORT_REGS->GROUP[0].PORT_PMUX[1] = PORT_PMUX_PMUXO_B | PORT_PMUX_PMUXE_B; // PA2 ADC_AIN0, PA3 ADC_AIN1
    PORT_REGS->GROUP[0].PORT_PMUX[2] = PORT_PMUX_PMUXO_B | PORT_PMUX_PMUXE_B; // PA4 ADC_AIN2, PA5 ADC_AIN5
    PORT_REGS->GROUP[0].PORT_PMUX[3] = PORT_PMUX_PMUXO_B | PORT_PMUX_PMUXE_B; // PA6 ADC_AIN6, PA7 ADC_AIN7
}

// *****************************************************************************
/* Initialize ADC module */
void ADC_Initialize ( void )
{
    ADC_ClockInit();
    ADC_PortInit();

    /* Reset ADC */
    ADC_REGS->ADC_CTRLA = ADC_CTRLA_SWRST_Msk;

    while (ADC_REGS->ADC_STATUS & ADC_STATUS_SYNCBUSY_Msk) {
        /* Wait for Synchronization */
    }

    uint32_t adc_linearity0 = (((* (uint64_t *)OTP4_ADDR) & (uint64_t)ADC_LINEARITY0_Msk) >> ADC_LINEARITY0_POS);
    uint32_t adc_linearity1 = (((* (uint64_t *) (OTP4_ADDR + 4)) & ADC_LINEARITY1_Msk) >> ADC_LINEARITY1_POS);

    /* Write linearity calibration and bias calibration */
    ADC_REGS->ADC_CALIB = (uint32_t) (ADC_CALIB_LINEARITY_CAL (adc_linearity0 | (adc_linearity1 << 5))) \
                          | ADC_CALIB_BIAS_CAL ((((* (uint64_t *) (OTP4_ADDR + 4)) & ADC_BIASCAL_Msk) >> ADC_BIASCAL_POS));

    /* Sampling length */
    ADC_REGS->ADC_SAMPCTRL = ADC_SAMPCTRL_SAMPLEN (19U);

    /* reference */
    ADC_REGS->ADC_REFCTRL = ADC_REFCTRL_REFSEL_AREFB;

    /* positive and negative input pins */
    ADC_REGS->ADC_INPUTCTRL = (uint32_t) ADC_POSINPUT_PIN0 | (uint32_t) ADC_NEGINPUT_GND \
                              | ADC_INPUTCTRL_INPUTSCAN (0) | ADC_INPUTCTRL_INPUTOFFSET (0) | ADC_INPUTCTRL_GAIN_1X;

    /* Prescaler, Resolution & Operation Mode */
    ADC_REGS->ADC_CTRLB = ADC_CTRLB_PRESCALER_DIV16 | ADC_CTRLB_RESSEL_16BIT ;

    /* Result averaging */
    ADC_REGS->ADC_AVGCTRL = ADC_AVGCTRL_SAMPLENUM_32 | ADC_AVGCTRL_ADJRES (4);

    /* Clear all interrupt flags */
    ADC_REGS->ADC_INTFLAG = ADC_INTFLAG_Msk;

    while (ADC_REGS->ADC_STATUS & ADC_STATUS_SYNCBUSY_Msk) {
        /* Wait for Synchronization */
    }
}

/* Enable ADC module */
void ADC_Enable ( void )
{
    ADC_REGS->ADC_CTRLA |= ADC_CTRLA_ENABLE_Msk;
    while (ADC_REGS->ADC_STATUS & ADC_STATUS_SYNCBUSY_Msk) {
        /* Wait for Synchronization */
    }
}

/* Disable ADC module */
void ADC_Disable ( void )
{
    ADC_REGS->ADC_CTRLA &= ~ADC_CTRLA_ENABLE_Msk;
    while (ADC_REGS->ADC_STATUS & ADC_STATUS_SYNCBUSY_Msk) {
        /* Wait for Synchronization */
    }
}

/* Configure channel input */
void ADC_ChannelSelect ( ADC_POSINPUT positiveInput, ADC_NEGINPUT negativeInput )
{
    /* Configure positive and negative input pins */
    uint32_t channel;
    channel = ADC_REGS->ADC_INPUTCTRL;
    channel &= ~ (ADC_INPUTCTRL_MUXPOS_Msk | ADC_INPUTCTRL_MUXNEG_Msk);
    channel |= (uint16_t) positiveInput | (uint16_t) negativeInput;
    ADC_REGS->ADC_INPUTCTRL = channel;

    while (ADC_REGS->ADC_STATUS & ADC_STATUS_SYNCBUSY_Msk) {
        /* Wait for Synchronization */
    }
}

/* Start the ADC conversion by SW */
void ADC_ConversionStart ( void )
{
    /* Start conversion */
    ADC_REGS->ADC_SWTRIG |= ADC_SWTRIG_START_Msk;

    while (ADC_REGS->ADC_STATUS & ADC_STATUS_SYNCBUSY_Msk) {
        /* Wait for Synchronization */
    }
}

/* Configure window comparison threshold values */
void ADC_ComparisonWindowSet (uint16_t low_threshold, uint16_t high_threshold)
{
    ADC_REGS->ADC_WINLT = low_threshold;
    ADC_REGS->ADC_WINUT = high_threshold;
    while (ADC_REGS->ADC_STATUS & ADC_STATUS_SYNCBUSY_Msk) {
        /* Wait for Synchronization */
    }
}

void ADC_WindowModeSet (ADC_WINMODE mode)
{
    ADC_REGS->ADC_WINCTRL = mode << ADC_WINCTRL_WINMODE_Pos;
    while (ADC_REGS->ADC_STATUS & ADC_STATUS_SYNCBUSY_Msk) {
        /* Wait for Synchronization */
    }
}


/* Read the conversion result */
uint16_t ADC_ConversionResultGet ( void )
{
    return (uint16_t)ADC_REGS->ADC_RESULT;
}


/* Check whether result is ready */
bool ADC_ConversionStatusGet ( void )
{
    bool status;
    status =  (bool) ((ADC_REGS->ADC_INTFLAG & ADC_INTFLAG_RESRDY_Msk) >> ADC_INTFLAG_RESRDY_Pos);
    if (status == true) {
        ADC_REGS->ADC_INTFLAG = ADC_INTFLAG_RESRDY_Msk;
    }
    return status;
}

#include <rtthread.h>

#if defined(RT_USING_DEVICE) && defined(RT_USING_ADC)

#include "rtdevice.h"

#define ADC_DEV_NAME "adc"          /* ADC 设备名称 */

struct rt_adc_device samd21_adc;    /* ADC 设备句柄 */
static rt_bool_t adc_enabled = RT_FALSE;

static rt_err_t drv_adc_enable (struct rt_adc_device *device, rt_uint32_t channel, rt_bool_t enabled)
{
    rt_bool_t *adc_en = (rt_bool_t *) (device->parent.user_data);

    channel = channel;
    if (enabled) {
        ADC_Enable();
        *adc_en = RT_TRUE;
    } else {
        ADC_Disable();
        *adc_en = RT_FALSE;
    }
    return RT_EOK;
}

static rt_err_t drv_adc_convert (struct rt_adc_device *device, rt_uint32_t channel, rt_uint32_t *value)
{
    rt_err_t result = RT_EOK;
    rt_bool_t *adc_en = (rt_bool_t *) (device->parent.user_data);

    RT_ASSERT (device);
    if (*adc_en != RT_TRUE) {
        ADC_Enable();
    }

    switch (channel) {
        case 0:
            ADC_ChannelSelect (ADC_POSINPUT_PIN0, ADC_NEGINPUT_GND);
            break;
        case 1:
            ADC_ChannelSelect (ADC_POSINPUT_PIN1, ADC_NEGINPUT_GND);
            break;
        case 2:
            ADC_ChannelSelect (ADC_POSINPUT_PIN5, ADC_NEGINPUT_GND);
            break;
        case 3:
            ADC_ChannelSelect (ADC_POSINPUT_PIN6, ADC_NEGINPUT_GND);
            break;
        case 4:
            ADC_ChannelSelect (ADC_POSINPUT_PIN7, ADC_NEGINPUT_GND);
            break;
        default:
            *value = 0x00;
            return RT_EEMPTY;
    }

    ADC_ConversionStart();
    while (ADC_ConversionStatusGet() == false);
    *value = ADC_ConversionResultGet();

    return result;
}

struct rt_adc_ops samd21_adc_ops = {
    drv_adc_enable,
    drv_adc_convert
};

static int samd21_rtt_adc_init (void)
{
    int result = RT_EOK;

    /* ADC init */
    ADC_Initialize();

    /* register adc device */
    if (rt_hw_adc_register ((rt_adc_device_t)&samd21_adc, ADC_DEV_NAME, &samd21_adc_ops, &adc_enabled) == RT_EOK) {
        rt_kprintf ("%s register success\n", ADC_DEV_NAME);
    } else {
        rt_kprintf ("%s register failed\n", ADC_DEV_NAME);
        result = -RT_ERROR;
    }

    return result;
}
INIT_DEVICE_EXPORT (samd21_rtt_adc_init);

#endif
